package com.kimstore.dao.impl;

import com.kimstore.bean.Order;
import com.kimstore.helper.EntityManagerHelper;

import java.io.Serializable;
import java.util.List;

import java.lang.reflect.ParameterizedType;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Example;

import com.kimstore.dao.GenericJpaDao;
import com.kimstore.utilities.QueryElements;
import java.util.Iterator;
import java.util.Map;

public abstract class GenericJpaDaoImpl<T, Id extends Serializable> implements
        GenericJpaDao<T, Id> {

    private Class<T> persistentClass;

    @SuppressWarnings("unchecked")
    public GenericJpaDaoImpl() {
        persistentClass = (Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
    }

    /**
     * Returns an entity through its primary key or id
     *
     * @param the primary key or id of an entity
     * @return an entity
     */
    public T findById(Id id) {
        T object = getEntityManager().find(getPersistentClass(), id);

        closeEntityManager();

        return object;
    }

    @SuppressWarnings("unchecked")
    /**
     * Finds all instances from a class
     *
     * @return a list of entities
     */
    public List<T> findAll() {
        List<T> objects = getEntityManager().createQuery(
                "from " + getPersistentClass().getSimpleName()).getResultList();

        closeEntityManager();

        return objects;
    }

    /**
     * Finds instances that accords with the given example
     *
     * @param an example instance
     * @return a list of entities
     */
    @SuppressWarnings("unchecked")
    public List<T> findByExample(T exampleInstance) {
        Example example = Example.create(exampleInstance);
        Session session = (Session) getEntityManager().getDelegate();
        Criteria crit = session.createCriteria(getPersistentClass()).add(
                example);
        List<T> objects = crit.list();

        closeEntityManager();

        return objects;
    }

    /**
     * Persists multiple entities to the database
     *
     * @param a list of entities
     */
    public void batchInsert(List<T> entityList) {
        beginTransaction();

        EntityManager entityManager = getEntityManager();
        for (int i = 0; i < entityList.size(); i++) {
            entityManager.persist(entityList.get(i));

            if (i % 20 == 0) {
                entityManager.flush();
                entityManager.clear();
            }
        }

        commit();

        closeEntityManager();
    }

    /**
     * Updates the given entity
     *
     * @param entity
     */
    public void saveOrUpdate(T entity) {
        beginTransaction();
        getEntityManager().merge(entity);
        commit();

        closeEntityManager();
    }

    /**
     * Deletes the given entity
     *
     * @param entity
     */
    public void delete(T entity) {
        beginTransaction();
        getEntityManager().remove(getEntityManager().merge(entity));
        commit();

        closeEntityManager();
    }

    /**
     * Deletes all entity from a class
     */
    public void deleteAll() {
        beginTransaction();
        getEntityManager().createQuery(
                "delete from " + getPersistentClass().getSimpleName())
                .executeUpdate();
        commit();

        closeEntityManager();
    }

    /**
     * Begins the transaction
     */
    protected void beginTransaction() {
        EntityManagerHelper.beginTransaction();
    }

    /**
     * Commits the changes made
     */
    protected void commit() {
        EntityManagerHelper.commit();
    }

    /**
     * Return the entityManager
     *
     * @return entityManager
     */
    protected EntityManager getEntityManager() {
        return EntityManagerHelper.getEntityManager();
    }

    /**
     * Closes the entityManager
     */
    protected void closeEntityManager() {
        EntityManagerHelper.closeEntityManager();
    }

    /**
     * Returns the persistent class
     *
     * @return the persistent class
     */
    private Class<T> getPersistentClass() {
        return persistentClass;
    }

    /**
     * Gets a single result from the query, if none return null
     *
     * @param query
     * @return a single result
     */
    @SuppressWarnings("unchecked")
    protected Object getSingleResultOrNull(Query query) {
        List<Object> results = query.getResultList();

        return (results.isEmpty()) ? null : results.get(0);
    }

    @Override
    public List<T> filter(QueryElements queryElements, long startIndex, long numberOfItems) {
        Query query = getEntityManager().createQuery(queryElements.getQuery());
        //System.out.println("1");
        if (queryElements.getParameters() != null) {
            Iterator it = queryElements.getParameters().entrySet().iterator();
            //System.out.println(queryElements.getParameters());
            while (it.hasNext()) {
                Map.Entry e = (Map.Entry) it.next();
                query.setParameter(e.getKey().toString(), e.getValue());
            }
        }
        //System.out.println("2");
        query.setFirstResult(Integer.parseInt(startIndex + ""));
        query.setMaxResults(Integer.parseInt(numberOfItems + ""));
        //System.out.println("3");
        List<T> results = query.getResultList();

        return results;
    }

    @Override
    public long count(QueryElements queryElements) {
        Query query = getEntityManager().createQuery("select count(*) " + queryElements.getQuery());

        if (queryElements.getParameters() != null) {
            Iterator it = queryElements.getParameters().entrySet().iterator();
            //System.out.println(queryElements.getParameters());
            while (it.hasNext()) {
                Map.Entry e = (Map.Entry) it.next();
                query.setParameter(e.getKey().toString(), e.getValue());
            }
        }
        long count = (long) query.getSingleResult();
        return count;
    }
}
